package com.zmn.oms.model.bo.work;

import com.google.common.collect.Lists;
import com.zmn.base.oms.common.model.es.orderwork.EsOrderWork;
import com.zmn.common.utils.logger.LoggerUtil;
import com.zmn.oms.model.entity.work.OrderWorkFilter;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.beanutils.BeanUtils;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.search.aggregations.*;
import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilter;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.ParsedSum;
import org.elasticsearch.search.aggregations.metrics.ParsedValueCount;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.SourceFilter;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * 简单聚合查询并映射工具
 * 不可用准备弃用
 * @param <T> 映射实体
 * @author hankangshuai
 */
@Deprecated
public class EsAggBuilder<T> {
    protected ElasticsearchRestTemplate esTemplate;
    private BuilderInit<T> builderInit;
    private final Logger logger = LoggerFactory.getLogger(BuilderInitImpl.class);
    private EsAggBuilderConfig<T> config = new EsAggBuilderConfig<>();
    /**
     * 重置的时候用
     */
    private EsAggBuilderConfig<T> configbak = new EsAggBuilderConfig<>();

    private Aggregations aggregations;

    private EsAggBuilder() {
        builderInit = new BuilderInitImpl();
    }

    /**
     * 记得加泛型呦
     * 记得加泛型呦
     * 记得加泛型呦
     */
    public static BuilderInit getBuilder() {
        return new EsAggBuilder().builderInit;
    }


    /**
     * 配置实体
     *
     * @param <T>
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class EsAggBuilderConfig<T> {


        /**
         * 结果集
         */
        protected Map<Integer, T> resultMap;

        /**
         * 查询条件对象
         */

        protected OrderWorkFilter query;

        /**
         * 默认查询条件备份
         */

        protected OrderWorkFilter defaultFilter;


        /**
         * 编译查询完成后的回调
         */
        protected BuiderCallback buildQueryCallback;


        /**
         * 分组依据
         */

        protected String groupingBy;

        /**
         * 结果集是否已被初始化（如果初始化，查询结果只会包含初始化集合时的数据，未初始化会把所有查询结果返回）
         */
        protected boolean alreadyInit = false;

    }

    public interface BuiderCallback extends BiConsumer<NativeSearchQuery, OrderWorkFilter> {

    }


    public interface BuilderInit<T> {

        /**
         * 初始化配置
         *
         * @param groupingBy         分组依据
         * @param buildQueryCallback 查询对象转换为原生查询对象回调
         * @param defaultCondition   默认查询条件(每次查询结束不会清空该对象中的条件)
         * @param initResultMap      初始化的结果集
         */
        BuilderWorker<T> init(ElasticsearchRestTemplate elasticsearchRestTemplate, String groupingBy, BuiderCallback buildQueryCallback, OrderWorkFilter defaultCondition, Map<Integer, T> initResultMap);

        /**
         * 初始化配置
         *
         * @param groupingBy         分组依据
         * @param buildQueryCallback 查询对象转换为原生查询对象回调
         * @param initResultMap      初始化的结果集
         */
        BuilderWorker<T> init(ElasticsearchRestTemplate elasticsearchRestTemplate, String groupingBy, BuiderCallback buildQueryCallback, Map<Integer, T> initResultMap);

        /**
         * 初始化配置
         *
         * @param groupingBy         分组依据
         * @param buildQueryCallback 查询对象转换为原生查询对象回调
         * @param defaultCondition   默认查询条件
         */
        BuilderWorker<T> init(ElasticsearchRestTemplate elasticsearchRestTemplate, String groupingBy, BuiderCallback buildQueryCallback, OrderWorkFilter defaultCondition);

        /**
         * 初始化配置
         *
         * @param groupingBy         分组依据
         * @param buildQueryCallback 查询对象转换为原生查询对象回调
         * @return
         */
        BuilderWorker<T> init(ElasticsearchRestTemplate elasticsearchRestTemplate, String groupingBy, BuiderCallback buildQueryCallback);

        /**
         * 初始化配置
         *
         * @param groupingBy 分组依据
         */
        BuilderWorker<T> init(ElasticsearchRestTemplate elasticsearchRestTemplate, String groupingBy);


    }

    private class BuilderInitImpl implements BuilderInit<T> {

        @Override
        public BuilderWorker<T> init(ElasticsearchRestTemplate elasticsearchRestTemplate, String groupingBy, BuiderCallback buildQueryCallback, OrderWorkFilter defaultCondition, Map<Integer, T> initResultMap) {
            config.resultMap = initResultMap;
            config.alreadyInit = true;
            config.defaultFilter = defaultCondition;
            return this.init(elasticsearchRestTemplate, groupingBy, buildQueryCallback);
        }

        @Override
        public BuilderWorker<T> init(ElasticsearchRestTemplate elasticsearchRestTemplate, String groupingBy, BuiderCallback buildQueryCallback, Map<Integer, T> initResultMap) {
            config.resultMap = initResultMap;
            config.alreadyInit = true;
            return this.init(elasticsearchRestTemplate, groupingBy, buildQueryCallback);
        }

        @Override
        public BuilderWorker<T> init(ElasticsearchRestTemplate elasticsearchRestTemplate, String groupingBy, BuiderCallback buildQueryCallback, OrderWorkFilter defaultCondition) {
            config.query = defaultCondition;
            return this.init(elasticsearchRestTemplate, groupingBy, buildQueryCallback);
        }

        @Override
        public BuilderWorker<T> init(ElasticsearchRestTemplate elasticsearchRestTemplate, String groupingBy, BuiderCallback buildQueryCallback) {
            config.buildQueryCallback = buildQueryCallback;
            return this.init(elasticsearchRestTemplate, groupingBy);
        }

        @Override
        public BuilderWorker<T> init(ElasticsearchRestTemplate elasticsearchRestTemplate, String groupingBy) {
            config.groupingBy = groupingBy;
            esTemplate = elasticsearchRestTemplate;
            backupConfig();
            return new BuilderWorkerImpl<T>();
        }

        private void backupConfig() {
            try {
                BeanUtils.copyProperties(configbak, config);
            } catch (IllegalAccessException | InvocationTargetException e) {
                ReflectionUtils.handleReflectionException(e);
            }
        }
    }

    public interface BuilderWorker<T> {
        /**
         * 查询
         *
         * @param filterAndAgg 聚合语句和过滤条件
         */
        BuilderWorker<T> query(Function<OrderWorkFilter, AggregationBuilder> filterAndAgg);

        /**
         * 将查询结果映射进对象
         * @param clazz
         */
        BuilderWorker<T> mapping(Class<T> clazz);

        /**
         * 将查询结果手动映射进对象
         * 除底层为sum,count外都需要设置回调函数手动处理
         *
         * @param resultsMappingCallback 查询结果集处理回调, 处理回调返回一个map,key为结果对象的属性，value为该属性要设置的值
         * @param clazz
         */
        BuilderWorker<T> mapping(Function<Aggregation, Map<String, Integer>> resultsMappingCallback, Class<T> clazz);

        /**
         * 获取结果(获取后会将配置文件还原到初始化状态)
         *
         * @return 结果集列表
         */
        List<T> getResult();
        /**
         * 获取结果Map(获取后会将配置文件还原到初始化状态)
         *
         * @return 结果集列表
         */
        Map<Integer,T> getResultMap();
    }

    private class BuilderWorkerImpl<R> implements BuilderWorker<T> {
        @SuppressWarnings("Duplicates")
        @Override
        public BuilderWorker<T> query(Function<OrderWorkFilter, AggregationBuilder> filterAndAgg) {
long t1 =System.currentTimeMillis();
            OrderWorkFilter filter = new OrderWorkFilter();
            if (Objects.nonNull(config.defaultFilter)) {
                try {
                    BeanUtils.copyProperties(filter, config.defaultFilter);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    ReflectionUtils.handleReflectionException(e);
                }
            }
            AggregationBuilder agg = filterAndAgg.apply(filter);
            if (Objects.isNull(agg)) {
                throw new NullPointerException("聚合是必须的，请在回调中设置聚合");
            }
            BoolQueryBuilder boolQueryBuilder = null;
            try {
                boolQueryBuilder = EsQueryBuilder.getQuery(filter);
            } catch (Exception e) {
                e.printStackTrace();
            }
            NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(boolQueryBuilder);
            nativeSearchQuery.setPageable(EsQueryBuilder.getPageRequest(filter));
            List<String> includesList = filter.getIncludes();
            List<String> excludesList = filter.getExcludes();
            String[] includes = {};
            String[] excludes = {};

            if (includesList != null) {
                includes = includesList.toArray(new String[0]);
            }

            if (excludesList != null) {
                excludes = excludesList.toArray(new String[0]);
            }

            if (includes.length > 0 || excludes.length > 0) {
                SourceFilter sourceFilter = new FetchSourceFilter(includes, excludes);
                nativeSearchQuery.addSourceFilter(sourceFilter);
            }

            if (Objects.nonNull(config.buildQueryCallback)) {
                config.buildQueryCallback.accept(nativeSearchQuery, filter);
            }
            String groupingBy = config.groupingBy;
            TermsAggregationBuilder terms = AggregationBuilders.terms(groupingBy).field(groupingBy).size(10000);
            terms.subAggregation(agg);
            nativeSearchQuery.addAggregation(terms);
            logger.debug(getQueryJson(nativeSearchQuery));
            aggregations = esTemplate.search(nativeSearchQuery, EsOrderWork.class).getAggregations();
            //todo 可能可以优化成queryForList,这样就不用处理映射实体的问题
            logger.info("es time:{} ms",  System.currentTimeMillis() - t1);
            return this;
        }

        @Override
        public BuilderWorker<T> mapping(Class<T> clazz) {
            LongTerms masterTerms = aggregations.get(config.groupingBy);
            List<LongTerms.Bucket> buckets = masterTerms.getBuckets();

            if (buckets != null && !buckets.isEmpty()) {
                for (LongTerms.Bucket e : buckets) {
                    Integer id = Integer.valueOf(e.getKeyAsString());
                    //如果结果集未初始化，则不进行key效验直接new对象填充
                    if (config.alreadyInit && !config.resultMap.containsKey(id)) {
                        continue;
                    }
                    Aggregations aggs = e.getAggregations();
                    Aggregation next = aggs.iterator().next();
                    Map<String, Integer> fieIdValueMap;
                    fieIdValueMap = defaultResultsMapping(next);
                    /*
                        取出结果集合中对应的对象
                        已初始化则直接取出赋值更新该对象，
                        未初始化取出来是空的，获取泛型类并实例化，然后填充放入结果集
                     */
                    processingResultMap(id, fieIdValueMap, clazz);
                }

            }
            return this;
        }

        @Override
        public BuilderWorker<T> mapping(Function<Aggregation, Map<String, Integer>> resultsMappingCallback, Class<T> clazz) {
            LongTerms masterTerms = aggregations.get(config.groupingBy);
            List<LongTerms.Bucket> buckets = masterTerms.getBuckets();

            if (buckets != null && !buckets.isEmpty()) {
                for (LongTerms.Bucket e : buckets) {
                    Integer id = Integer.valueOf(e.getKeyAsString());
                    //如果结果集未初始化，则不进行key效验直接new对象填充
                    if (config.alreadyInit && !config.resultMap.containsKey(id)) {
                        continue;
                    }
                    Aggregations aggs = e.getAggregations();
                    Aggregation next = aggs.iterator().next();
                    Map<String, Integer> fieIdValueMap = resultsMappingCallback.apply(next);

                    /*
                        取出结果集合中对应的对象
                        已初始化则直接取出赋值更新该对象，
                        未初始化取出来是空的，获取泛型类并实例化，然后填充放入结果集
                     */
                    processingResultMap(id, fieIdValueMap, clazz);
                }

            }
            return this;
        }

        private void processingResultMap(Integer id, Map<String, Integer> fieIdValueMap, Class<T> clazz) {
            T t = config.resultMap.get(id);
            try {
                if (Objects.isNull(t)) {
                    t=clazz.newInstance();
                }
                BeanUtils.populate(t, fieIdValueMap);
                config.resultMap.put(id, t);
            } catch (Exception ex) {
                ReflectionUtils.handleReflectionException(ex);
            }
        }

        @Override
        public List<T> getResult() {
            ArrayList<T> result = Lists.newArrayList(config.resultMap.values());
            resetConfig();
            return result;
        }

        @Override
        public Map<Integer,T> getResultMap() {
            return config.resultMap;
        }


        /**
         * 默认的结果集处理，只能处理聚合最底层为单个的sum或count操作（上层可以有filter）
         */
        private Map<String, Integer> defaultResultsMapping(Aggregation aggregation) {
            String type = aggregation.getType();
            switch (type) {
                case "sum":
                    double sum = ((ParsedSum) aggregation).getValue();
                    return Collections.singletonMap(aggregation.getName(), (int) sum);
                case "value_count":
                    long count = ((ParsedValueCount) aggregation).getValue();
                    String name = aggregation.getName();
                    return Collections.singletonMap(name, (int) count);
                case "value_filter":
                    Aggregations aggregations = ((ParsedFilter) aggregation).getAggregations();
                    Map<String, Aggregation> asMap = aggregations.getAsMap();
                    Map.Entry<String, Aggregation> next = asMap.entrySet().iterator().next();
                    Aggregation value = next.getValue();
                    return defaultResultsMapping(value);
                //todo 其他聚合待实现
                default:
                    LoggerUtil.error(getClass(), "需要设置回调函数");
                    return null;
            }
        }

        /**
         * 重置配置
         */
        private void resetConfig() {
            try {
                BeanUtils.copyProperties(config, configbak);
            } catch (IllegalAccessException | InvocationTargetException e) {
                ReflectionUtils.handleReflectionException(e);
            }
        }

        /**
         * NativeSearchQuery转ESJsonDSL，便于调试
         *
         * @param query 要转换的NativeSearchQuery
         * @return ESJsonDSL语句
         */
        private String getQueryJson(NativeSearchQuery query) {
            String jsonTemplate = "{\n" +
                    "  \"query\": %s,\n" +
                    "  \"from\": %s,\n" +
                    "  \"size\": %s,\n" +
                    "  \"sort\": [%s],\n" +
                    "  \"aggs\": %s \n" +
                    "}";

            String queryPart = Objects.isNull(query.getQuery()) ? "" : query.getQuery().toString();
            String sortPart = Objects.isNull(query.getSort()) ? "" : query.getSort().toString();
            int formPart = query.getPageable().getPageNumber();
            int sizePart = query.getPageable().getPageSize();
            String aggPart = "";
            if (query.getAggregations() != null) {
                try {
                    XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
                    builder.startObject();
                    for (AbstractAggregationBuilder subAgg : query.getAggregations()) {
                        subAgg.toXContent(builder, ToXContent.EMPTY_PARAMS);
                    }
                    builder.endObject();
                    aggPart = builder.toString();
                } catch (IOException e) {
                    logger.debug("Error parsing aggs");
                }
            }
            return String.format(jsonTemplate, queryPart, formPart, sizePart, sortPart, aggPart);
        }


    }


}
